package cc.shacocloud.kotlin.tools.cli

import cc.shacocloud.kotlin.tools.Slf4j
import org.eclipse.aether.repository.RemoteRepository
import org.eclipse.aether.util.repository.AuthenticationBuilder
import java.io.File
import kotlin.script.experimental.api.*
import kotlin.script.experimental.dependencies.ExternalDependenciesResolver
import kotlin.script.experimental.dependencies.RepositoryCoordinates
import kotlin.script.experimental.dependencies.impl.*

/**
 * 基于 maven 试下你的依赖解析器
 *
 * @author 思追(shaco)
 */
@Slf4j
class MavenDependenciesResolver(
    private val mavenRepository: MavenRepository
) : ExternalDependenciesResolver {

    private val remoteRepositoryList = mutableListOf<RemoteRepository>()

    /**
     * 依赖是否可以被接受
     */
    override fun acceptsArtifact(artifactCoordinates: String): Boolean {
        return MavenRepository.isArtifacts(artifactCoordinates)
    }

    /**
     * 存储库是否可以被接受
     */
    override fun acceptsRepository(repositoryCoordinates: RepositoryCoordinates): Boolean {
        return repositoryCoordinates.toRepositoryUrlOrNull() != null
    }

    /**
     * 添加存储库
     */
    override fun addRepository(
        repositoryCoordinates: RepositoryCoordinates,
        options: ExternalDependenciesResolver.Options,
        sourceCodeLocation: SourceCode.LocationWithId?
    ): ResultWithDiagnostics<Boolean> {

        val reports = mutableListOf<ScriptDiagnostic>()

        val username = getFinalValue("username", options.username, reports, sourceCodeLocation)
        val password = getFinalValue("password", options.password, reports, sourceCodeLocation)
        val privateKeyFile = getFinalValue("private key file", options.privateKeyFile, reports, sourceCodeLocation)
        val privateKeyPassphrase =
            getFinalValue("private key passphrase", options.privateKeyPassphrase, reports, sourceCodeLocation)

        if (reports.isNotEmpty()) {
            return ResultWithDiagnostics.Failure(reports)
        }

        // 如果存在一个认证配置，则进行认证
        val authentication = if (
            !username.isNullOrBlank() || !password.isNullOrBlank()
            || !privateKeyFile.isNullOrBlank() || !privateKeyPassphrase.isNullOrBlank()
        ) {
            AuthenticationBuilder()
                .addUsername(username)
                .addPassword(password)
                .addPrivateKey(
                    privateKeyFile,
                    privateKeyPassphrase
                )
                .build()
        } else null

        // 添加存储库
        val repository = MavenRepository.toRemoteRepository(
            url = repositoryCoordinates.string,
            authentication = authentication
        )
        remoteRepositoryList.add(repository)

        return true.asSuccess()
    }

    /**
     * 解析依赖
     */
    override suspend fun resolve(
        artifactCoordinates: String,
        options: ExternalDependenciesResolver.Options,
        sourceCodeLocation: SourceCode.LocationWithId?
    ): ResultWithDiagnostics<List<File>> {

        return try {
            val reports = mutableListOf<ScriptDiagnostic>()

            val files = mavenRepository.download(
                artifacts = artifactCoordinates,
                repositoryList = remoteRepositoryList + MavenRepository.defaultRemoteRepository,
                warningsCallback = {
                    if (!it.message.isNullOrBlank()) {
                        reports.add(
                            ScriptDiagnostic(
                                ScriptDiagnostic.unspecifiedError,
                                it.message!!,
                                ScriptDiagnostic.Severity.WARNING,
                                sourceCodeLocation
                            )
                        )
                    }
                }
            )

            ResultWithDiagnostics.Success(files, reports)
        } catch (e: Exception) {
            ResultWithDiagnostics.Failure(
                listOf(
                    ScriptDiagnostic(
                        ScriptDiagnostic.unspecifiedError,
                        e.message ?: "依赖 $artifactCoordinates 下载失败！",
                        ScriptDiagnostic.Severity.ERROR,
                        sourceCodeLocation,
                        e
                    )
                )
            )
        }
    }


    /**
     * 获取最终的值
     */
    private fun getFinalValue(
        optionName: String,
        rawValue: String?,
        reports: MutableList<ScriptDiagnostic>,
        sourceCodeLocation: SourceCode.LocationWithId?
    ): String? {
        return tryResolveEnvironmentVariable(rawValue, optionName, sourceCodeLocation)
            .onFailure { reports.addAll(it.reports) }
            .valueOrNull()
    }

    /**
     * 尝试从环境变量中解析值
     */
    private fun tryResolveEnvironmentVariable(
        str: String?,
        optionName: String,
        location: SourceCode.LocationWithId?
    ): ResultWithDiagnostics<String?> {
        if (str == null) return null.asSuccess()
        if (!str.startsWith("$")) return str.asSuccess()

        val envName = str.substring(1)
        val envValue: String? = System.getenv(envName)

        if (envValue.isNullOrEmpty()) {
            return ResultWithDiagnostics.Failure(
                ScriptDiagnostic(
                    ScriptDiagnostic.unspecifiedError,
                    "$optionName 未能从环境变量 `$envName` 获取值！",
                    ScriptDiagnostic.Severity.ERROR,
                    location
                )
            )
        }

        return envValue.asSuccess()
    }
}